package org.activiti.solon.service.impl;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.activiti.solon.constants.AuditFlagEnum;
import org.activiti.solon.constants.ExtConstants;
import org.activiti.solon.entity.ActExtDef;
import org.activiti.solon.entity.AssignVO;
import org.activiti.solon.entity.FlowEditVO;
import org.activiti.solon.entity.PageExt;
import org.activiti.solon.exception.ServiceException;
import org.activiti.solon.mapper.ActExtDefMapper;
import org.activiti.solon.service.ActExtDefService;
import org.activiti.solon.service.ActUserService;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.solon.annotation.Db;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
import org.noear.snack.ONode;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.data.annotation.Tran;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.Date;

@Slf4j
@Component
public class ActExtDefServiceImpl implements ActExtDefService {
    @Db("master")
    private ActExtDefMapper actExtDefMapper;

    @Inject
    private ActUserService actUserService;

    @Inject
    private RepositoryService repositoryService;

    @Override
    public int insert(ActExtDef actExtDef) {
        long uid = IdUtil.getSnowflake().nextId();
        actExtDef.setUid(BigInteger.valueOf(uid));
        actExtDef.setCreateTime(new Date());
        actExtDef.setUpdateTime(new Date());
        actExtDef.setFlowVersion(0);
        actExtDef.setAuditFlag(AuditFlagEnum.SUBMIT.getVal());
        return actExtDefMapper.insert(actExtDef);
    }

    @Override
    public String create(ActExtDef actExtDef) {
        // 验证流程编码唯一
        LambdaQueryWrapper<ActExtDef> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ActExtDef::getFlowCode, actExtDef.getFlowCode())
                .eq(ActExtDef::getAreaCode, actExtDef.getAreaCode());
        if (actExtDefMapper.selectCount(queryWrapper) > 0) {
            throw new ServiceException("Error creating model: flowCode is unique");
        }

        ONode modelObjectNode = ONode.newObject();
        modelObjectNode.set(ExtConstants.NAME, actExtDef.getFlowCode());
        modelObjectNode.set(ExtConstants.REVISION, 1);
        String description = actExtDef.getFlowName();
        modelObjectNode.set(ExtConstants.DESCRIPTION, description);
        Model modelData = repositoryService.newModel();
        modelData.setMetaInfo(modelObjectNode.toString());
        modelData.setName(actExtDef.getFlowName());
        modelData.setKey(actExtDef.getFlowCode());
        repositoryService.saveModel(modelData);

        // 设置空白编辑模板
        ONode editorNode = ONode.newObject();
        editorNode.set("id", "canvas");
        editorNode.set("resourceId", "canvas");
        ONode stencilSetNode = ONode.newObject();
        stencilSetNode.set("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
        editorNode.set("stencilset", stencilSetNode);
        // 设置属性节点
        ONode propertiesNode = ONode.newObject();
        propertiesNode.set("process_id", actExtDef.getFlowCode()); // 唯一标识符
        propertiesNode.set("name", actExtDef.getFlowName());
        propertiesNode.set("documentation", actExtDef.getRemark());
        propertiesNode.set("process_version", 0);
        propertiesNode.set("process_author", actExtDef.getCreateBy());
        propertiesNode.set("process_type", 1); // 类型 1：指定
        editorNode.set("properties", propertiesNode);
        repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes(StandardCharsets.UTF_8));

        actExtDef.setActModelId(modelData.getId());
        actExtDef.setCreateTime(new Date());
        actExtDef.setUpdateTime(new Date());
        int count = this.insert(actExtDef);
        if (count < 1) {
            throw new ServiceException("Error creating ActExtDef");
        }
        return modelData.getId();
    }

    @Override
    public ONode selectEditorJson(String modelId) {
        Model model = repositoryService.getModel(modelId);
        ONode modelNode = null;
        if (model != null) {
            try {
                if (StringUtils.isNotEmpty(model.getMetaInfo())) {
                    modelNode = ONode.loadStr(model.getMetaInfo());
                } else {
                    modelNode = new ONode();
                    modelNode.set("name", model.getName());
                }
                modelNode.set("modelId", model.getId());
                byte[] editorSource = repositoryService.getModelEditorSource(model.getId());
                ONode editorJsonNode = ONode.loadStr(new String(editorSource, StandardCharsets.UTF_8));
                modelNode.set("model", editorJsonNode);
            } catch (Exception e) {
                log.error("Error creating model JSON", e);
                throw new ServiceException("Error creating model JSON");
            }
        }
        return modelNode;
    }

    @Tran
    @Override
    public String saveEditorXml(FlowEditVO flowEdit) {
        try {
            String actModeId = flowEdit.getActModelId();
            // 查询原来的流程定义
            Model model = repositoryService.getModel(actModeId);

            // 取得扩展的流程定义
            ActExtDef defInfo = actExtDefMapper.selectExtDefInfoByFLowId(actModeId);

            log.debug("svgXml:{}", flowEdit.getSvgXml());

            log.debug("jsonXml:{}", flowEdit.getJsonXml());

            Model thisModel = updateDef(defInfo, model, flowEdit);
            // 不是发布，覆盖原ModelId
            log.info("覆盖原ModelId：{}", defInfo.getActModelId());
            return thisModel.getId();
        } catch (Exception e) {
            log.error("Error saving model", e);
            throw new ServiceException("Error saving model");
        }

    }

    @Override
    public IPage<AssignVO> queryAssign(PageExt<AssignVO> queryInfo) {
        AssignVO query = queryInfo.getQueryObject();
        if (StringUtils.equals(query.getType(), "USER")) {
            return actUserService.queryUserPage(queryInfo, query);
        } else if (StringUtils.equals(query.getType(), "GROUP"))  {
            return actUserService.queryGroupPage(queryInfo, query);
        } else if (StringUtils.equals(query.getType(), "ROLE"))  {
            return actUserService.queryRolePage(queryInfo, query);
        }
        throw new ServiceException("Not Support Type");
    }

    @Override
    public IPage<ActExtDef> selectPage(PageExt<ActExtDef> queryInfo) {
        QueryWrapper<ActExtDef> queryWrapper = new QueryWrapper<>();
        queryWrapper.setEntity(queryWrapper.getEntity());
        return actExtDefMapper.selectPage(queryInfo, queryWrapper);
    }

    @Override
    public String deployModel(BigInteger extDefId) {
        ActExtDef defInfo = actExtDefMapper.selectById(extDefId);
        String modelId = defInfo.getActModelId();
        if (StringUtils.isNotEmpty(defInfo.getActFlowId())) {
            Model oldModel = repositoryService.getModel(modelId);
            // 复制老模型的所有信息
            Model newModel = createNewMode(oldModel);
            String proId = deployAndGetDefinitionQId(newModel.getId());
            ActExtDef update = new ActExtDef();
            update.setActModelId(newModel.getId());
            update.setActFlowId(proId);
            update.setUid(defInfo.getUid());
            update.setFlowVersion(defInfo.getFlowVersion() + 1);
            updateById(update);
            return proId;
        } else {
            String proId = deployAndGetDefinitionQId(modelId);
            ActExtDef update = new ActExtDef();
            update.setActFlowId(proId);
            update.setUid(defInfo.getUid());
            update.setFlowVersion(0);
            updateById(update);
            return proId;
        }
    }

    @Override
    public String selectModelSvg(String modelId) {
        byte[] svgExtra = repositoryService.getModelEditorSourceExtra(modelId);
        return new String(svgExtra, StandardCharsets.UTF_8);
    }

    private String deployAndGetDefinitionQId(String modelId) {
        Deployment deployment = doDeployModel(modelId);
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deployment.getId()).singleResult();
        if (processDefinition == null) {
            throw new ServiceException("create ProcessDefinition error");
        }
        return processDefinition.getId();
    }

    private Model createNewMode(Model old) {
        Model newModel = repositoryService.newModel();
        newModel.setMetaInfo(old.getMetaInfo());
        newModel.setName(old.getName());
        newModel.setKey(old.getKey());
        repositoryService.saveModel(newModel);
        // 通过SVG文件内容来生成PNG，并且加入至资源文件中
        byte[] oldSource = repositoryService.getModelEditorSource(old.getId());
        repositoryService.addModelEditorSource(newModel.getId(), oldSource);
        // 增加两附加资源文件
        byte[] oldExtra = repositoryService.getModelEditorSourceExtra(old.getId());
        repositoryService.addModelEditorSourceExtra(newModel.getId(), oldExtra);
        return newModel;
    }

    private Deployment doDeployModel(String modelId) {
        Model modelData = repositoryService.getModel(modelId);
        String processName = modelData.getKey() + ".bpmn20.xml";
        String xmlJson = new String(repositoryService.getModelEditorSource(modelId));
        BpmnModel flowXml = convertJsonToXml(xmlJson);
        log.debug("flowXml: {}", flowXml);
        Deployment deployment = repositoryService.createDeployment()
                .name(processName)
                .addBpmnModel(processName, flowXml)
                .deploy();
        modelData.setDeploymentId(deployment.getId());
        repositoryService.saveModel(modelData);
        return deployment;
    }

    private BpmnModel convertJsonToXml(String jsonXml) {
        try {
            JsonNode jsonNode = new ObjectMapper().readTree(jsonXml);
            BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
            return jsonConverter.convertToBpmnModel(jsonNode);
        } catch (Exception ex) {
            log.error("convertJsonToXml error", ex);
            throw new ServiceException("convertJsonToXml error");
        }
    }

    /**
     * 更新模块的名称及描述，同时更新Activiti模块的设计文件
     */
    private Model updateDef(ActExtDef defInfo, Model model, FlowEditVO flowEdit) {
        Model thisModel = updateModel(model, flowEdit);
        if (defInfo != null) {
            ActExtDef update = new ActExtDef();
            update.setActModelId(thisModel.getId());
            update.setUid(defInfo.getUid());
            update.setFlowName(flowEdit.getName());
            update.setRemark(flowEdit.getDescription());
            updateById(update);
        }

        return thisModel;
    }

    /**
     * 更新Model的设计内容
     */
    private Model updateModel(Model model, FlowEditVO flowEdit) {
        ONode modelJson = ONode.loadStr(model.getMetaInfo());
        modelJson.set(ExtConstants.NAME, flowEdit.getName());
        modelJson.set(ExtConstants.DESCRIPTION, flowEdit.getDescription());
        if (StringUtils.isEmpty(model.getDeploymentId())) {
            model.setMetaInfo(modelJson.toString());
            model.setName(flowEdit.getName());
            // 加入设计文件
            repositoryService.addModelEditorSource(model.getId(), flowEdit.getJsonXml().getBytes(StandardCharsets.UTF_8));
            repositoryService.addModelEditorSourceExtra(model.getId(), flowEdit.getSvgXml().getBytes(StandardCharsets.UTF_8));
            return model;
        } else {
            Model newModel = repositoryService.newModel();
            newModel.setMetaInfo(modelJson.toString());

            newModel.setName(flowEdit.getName());
            newModel.setKey(model.getKey());
            repositoryService.saveModel(newModel);

            ONode editorJson = ONode.loadStr(flowEdit.getJsonXml());
            editorJson.set(ExtConstants.RESOURCE_ID, newModel.getId());
            // 加入设计文件
            repositoryService.addModelEditorSource(newModel.getId(), editorJson.toString().getBytes(StandardCharsets.UTF_8));
            repositoryService.addModelEditorSourceExtra(newModel.getId(), flowEdit.getSvgXml().getBytes(StandardCharsets.UTF_8));
            return newModel;
        }
    }

    private void updateById(ActExtDef update) {
        actExtDefMapper.updateById(update);
    }
}
